package com.sd.repay.service.impl;

import com.sd.repay.common.CommonResult;
import com.sd.repay.common.Constants;
import com.sd.repay.dao.BaseDao;
import com.sd.repay.pojo.Balance;
import com.sd.repay.pojo.BalanceHis;
import com.sd.repay.service.BalanceOperNotTranService;
import com.sd.repay.service.BalanceOperService;
import com.sd.repay.utils.DES3Coder;
import com.sd.repay.utils.JsonUtil;
import com.sd.repay.utils.StringUtil;
import com.sd.repay.utils.WebUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * Created by sd on 2018/08/21
 * 用户余额Service接口实现
 */
@Service
public class BalanceOperServiceImpl implements BalanceOperService {

    private static Logger log = LoggerFactory.getLogger(BalanceOperServiceImpl.class);

    @Autowired
    private BaseDao baseDao;
    @Autowired
    private BalanceOperNotTranService balanceOperNotTranService;


    /**
     * 根据初始化的余额流水变动余额
     *
     * @param merNo          业务商户编号
     * @param merType        业务商户编号
     * @param amount         变动金额
     * @param transType      金额变动类型  IN钱包入账   OUT钱包出账
     * @param balanceType    操作余额类型 curr_balance：今日余额；his_balance：历史余额；freeze_balance：冻结金额'
     * @param service        变动来源业务 0：交易；1：提现；2：冲正；3：调账；4：分润'
     * @param serviceOrderNo 变动来源业务单号
     * @param remark         备注
     * @param balanceChannel 余额通道类型  代付余额：DaiFu
     * @return
     * @throws Exception
     */
    @Override
    @Transactional
    public CommonResult updateBalance(String merNo, String merType, BigDecimal amount, String transType, String balanceType, String service, String serviceOrderNo, String remark, String balanceChannel) throws Exception {


        log.info("更新余额，merNo:{},merType:{},amount:{},transType:{},balanceType:{},service:{},serviceOrderNo:{},remark:{},balanceChannel:{}",
                new Object[]{merNo, merType, amount, transType, balanceType, service, serviceOrderNo, remark, balanceChannel});

        if (StringUtil.isBlank(merNo, merType, transType, balanceType, service, serviceOrderNo, remark, balanceChannel)) {
            throw new RuntimeException("参数不能有空");
        }
        if (amount == null) {
            throw new RuntimeException("参数不能有空");
        }
        if (amount.compareTo(new BigDecimal("0")) < 0 || (!Constants.BALANCE_HIS_TRANS_TYPE_IN.equals(transType) && !Constants.BALANCE_HIS_TRANS_TYPE_OUT.equals(transType))
                || (!Constants.BALANCE_HIS_TYPE_CURR.equals(balanceType) && !Constants.BALANCE_HIS_TYPE_HIS.equals(balanceType) && !Constants.BALANCE_HIS_TYPE_FREEZE.equals(balanceType))) {
            throw new RuntimeException("参数不合法");
        }

        amount = amount.setScale(2, BigDecimal.ROUND_HALF_UP);
        String balanceNo = "";
        int executeCount = 0;

        //查询此用户此通道是否开户
        Balance balance = getAccountByMerAndChannel(merNo, merType, balanceChannel);
        if (balance == null) {

            balanceNo = WebUtil.nextUniqueSeqNo("t_balance", "BAL", 20);
            balance = new Balance();
            balance.setBalanceNo(balanceNo);
            balance.setMerType(merType);
            balance.setMerNo(merNo);
            balance.setBalanceChannel(balanceChannel);

            int count = balanceOperNotTranService.insertBalanceNotTransactional(balance);
            if (count < 1) {
                throw new RuntimeException("初始化余额账户失败");
            }
        }

        String sql = " UPDATE t_balance SET row_lock = row_lock + 1 WHERE mer_type = ? AND mer_no = ? AND balance_channel = ? ";
        executeCount = baseDao.execute(sql, new Object[]{merType, merNo, balanceChannel});
        if (executeCount < 1) {
            throw new RuntimeException("锁定余额账户失败");
        }

        balance = getAccountByMerAndChannel(merNo, merType, balanceChannel);
        if (balance == null) {
            throw new RuntimeException("初始化余额账户失败");
        }

        balanceNo = balance.getBalanceNo();
        String balanceStatus = balance.getStatus();
        BigDecimal currBalance = balance.getCurrBalance();
        BigDecimal hisBalance = balance.getHisBalance();
        BigDecimal freezeBalance = balance.getFreezeBalance();
        String encryptTotalBalance = balance.getEncryptTotalBalance();

        BigDecimal nowBalanceFront = currBalance.add(hisBalance);

        //校验是否已有此服务订单记录
        sql = " SELECT * FROM t_balance_his WHERE service = ? AND service_order_no = ? AND result_status = ? ";
        BalanceHis balanceHis = baseDao.findFirst(BalanceHis.class, sql, new Object[]{service, serviceOrderNo, Constants.BALANCE_HIS_STATUS_SUCCESS});
        if (balanceHis != null) {
            throw new RuntimeException("已有此成功的服务订单记录,service:" + service + ",serviceOrderNo:" + serviceOrderNo);
        }

        //计算变动之后的余额
        BigDecimal amountBehind = BigDecimal.ZERO;
        if (Constants.BALANCE_HIS_TRANS_TYPE_IN.equals(transType)) {
            amountBehind = nowBalanceFront.add(amount);

        } else if (Constants.BALANCE_HIS_TRANS_TYPE_OUT.equals(transType)) {
            amountBehind = nowBalanceFront.subtract(amount);
        }
        String encryptTotalBalanceBehind = DES3Coder.encryptToString(merType + merNo + String.valueOf(amountBehind));

        //初始化余额流水明细
        String hisNo = WebUtil.nextUniqueSeqNo("t_balance_his", "BALH", 20);

        BalanceHis insertBalanceHis = new BalanceHis();
        insertBalanceHis.setHisNo(hisNo);
        insertBalanceHis.setBalanceNo(balanceNo);
        insertBalanceHis.setMerType(merType);
        insertBalanceHis.setMerNo(merNo);
        insertBalanceHis.setAmountFront(nowBalanceFront);
        insertBalanceHis.setAmount(amount);
        insertBalanceHis.setAmountBehind(amountBehind);
        insertBalanceHis.setTransType(transType);
        insertBalanceHis.setBalanceType(balanceType);
        insertBalanceHis.setService(service);
        insertBalanceHis.setServiceOrderNo(serviceOrderNo);
        insertBalanceHis.setRemark(remark);

        //校验余额是否异常
        String validateBalanceStr = merType + merNo + String.valueOf(nowBalanceFront);
        if (!DES3Coder.verify(validateBalanceStr, encryptTotalBalance)) {

            insertBalanceHis.setResultStatus(Constants.BALANCE_HIS_STATUS_FAIL);
            insertBalanceHis.setResultMsg("钱包金额异常,balanceNo:" + balanceNo + ",validateBalanceStr:" + validateBalanceStr + ",encryptTotalBalance:" + encryptTotalBalance);

            balanceOperNotTranService.insertBalanceHisNotTransactional(insertBalanceHis);
            throw new RuntimeException("钱包金额异常,balanceNo:" + balanceNo + ",validateBalanceStr:" + validateBalanceStr + ",encryptTotalBalance:" + encryptTotalBalance);
        }

        //出账时校验钱包状态（锁定不能出账）
        if (Constants.BALANCE_HIS_TRANS_TYPE_OUT.equals(transType) && Constants.BALANCE_STATUS_LOCK.equals(balanceStatus)) {

            insertBalanceHis.setResultStatus(Constants.BALANCE_HIS_STATUS_FAIL);
            insertBalanceHis.setResultMsg("钱包已锁定，不能出账，balanceNo：" + balanceNo + "，balanceStatus：" + balanceStatus);

            balanceOperNotTranService.insertBalanceHisNotTransactional(insertBalanceHis);
            throw new RuntimeException("钱包已锁定，不能出账，balanceNo：" + balanceNo + "，balanceStatus：" + balanceStatus);
        }

        //出账时校验余额是否足够(包含冻结金额校验)
        if (Constants.BALANCE_HIS_TRANS_TYPE_OUT.equals(transType) || Constants.BALANCE_HIS_SERVICE_CASH.equals(service)) {
            //可出款余额，今日余额+历史余额-冻结金额
            BigDecimal allowOutAmount = nowBalanceFront.subtract(freezeBalance);

            if (amount.compareTo(allowOutAmount) > 0 && !Constants.BALANCE_HIS_SERVICE_ADJUST.equals(service)) {

                insertBalanceHis.setResultStatus(Constants.BALANCE_HIS_STATUS_FAIL);
                insertBalanceHis.setResultMsg("大于可出账金额，balanceNo：" + balanceNo + "amount：" + amount + "> allowOutAmount：" + allowOutAmount);

                balanceOperNotTranService.insertBalanceHisNotTransactional(insertBalanceHis);
                throw new RuntimeException("大于可出账金额，balanceNo：" + balanceNo + "amount：" + amount + "> allowOutAmount：" + allowOutAmount);

            }
            if (Constants.BALANCE_HIS_TYPE_CURR.equals(balanceType) && amount.compareTo(currBalance) > 0) {

                insertBalanceHis.setResultStatus(Constants.BALANCE_HIS_STATUS_FAIL);
                insertBalanceHis.setResultMsg("可用今日余额不足,balanceNo:" + balanceNo + "amount：" + amount + "> currBalance：" + currBalance);

                balanceOperNotTranService.insertBalanceHisNotTransactional(insertBalanceHis);
                throw new RuntimeException("可用今日余额不足,balanceNo:" + balanceNo + "amount：" + amount + "> currBalance：" + currBalance);

            }
            if (Constants.BALANCE_HIS_TYPE_HIS.equals(balanceType) && amount.compareTo(hisBalance) > 0) {

                insertBalanceHis.setResultStatus(Constants.BALANCE_HIS_STATUS_FAIL);
                insertBalanceHis.setResultMsg("可用今日余额不足,balanceNo:" + balanceNo + "amount：" + amount + "> hisBalance：" + hisBalance);

                balanceOperNotTranService.insertBalanceHisNotTransactional(insertBalanceHis);
                throw new RuntimeException("可用历史余额不足,balanceNo:" + balanceNo + "amount：" + amount + "> hisBalance：" + hisBalance);

            }
        }

        //更新余额
        StringBuffer updateBalanceSql = new StringBuffer(" UPDATE t_balance SET ");
        updateBalanceSql.append(balanceType).append(" = ").append(balanceType);

        List<Object> updateBalanceParams = new ArrayList<>();
        updateBalanceParams.add(amount);
        updateBalanceParams.add(encryptTotalBalanceBehind);
        updateBalanceParams.add(balanceNo);


        if (Constants.BALANCE_HIS_TRANS_TYPE_IN.equals(transType)) {

            updateBalanceSql.append(" + ?, encrypt_total_balance = ? WHERE balance_no = ? ");
        } else if (Constants.BALANCE_HIS_TRANS_TYPE_OUT.equals(transType)) {

            updateBalanceSql.append(" - ?, encrypt_total_balance = ? WHERE balance_no = ? AND ").append(balanceType).append(" >= ? ");
            updateBalanceParams.add(amount);
        }
        executeCount = baseDao.execute(updateBalanceSql.toString(), updateBalanceParams.toArray());
        log.info("余额更新sql：updateBalanceSql=" + updateBalanceSql.toString() + "，参数信息updateBalanceList=" + JsonUtil.objectToJson(updateBalanceParams) + "影响的行数：" + executeCount);

        if (executeCount != 1) {

            insertBalanceHis.setResultStatus(Constants.BALANCE_HIS_STATUS_FAIL);
            insertBalanceHis.setResultMsg("更新余额影响行数异常，balanceNo：" + balanceNo + "，executeCount：" + executeCount);

            balanceOperNotTranService.insertBalanceHisNotTransactional(insertBalanceHis);
            throw new RuntimeException("更新余额影响行数异常，balanceNo：" + balanceNo + "，executeCount：" + executeCount);
        }

        insertBalanceHis.setResultStatus(Constants.BALANCE_HIS_STATUS_SUCCESS);
        insertBalanceHis.setResultMsg("更新余额成功");
        //成功插入流水
        Long insertId = insertBalanceHisInTransactionalResId(insertBalanceHis);
        if (insertId == null) {

            insertBalanceHis.setResultStatus(Constants.BALANCE_HIS_STATUS_FAIL);
            insertBalanceHis.setResultMsg("更新余额成功失败，balanceNo：" + balanceNo);

            balanceOperNotTranService.insertBalanceHisNotTransactional(insertBalanceHis);
            throw new RuntimeException("更新余额成功失败，balanceNo：" + balanceNo);
        }

        return CommonResult.ok();
    }

    /**
     * 以事务方式插入余额流水明细
     *
     * @param insertBalanceHis
     * @return
     * @throws Exception
     */
    @Override
    public Long insertBalanceHisInTransactionalResId(BalanceHis insertBalanceHis) throws Exception {

        String insertSql = " INSERT INTO t_balance_his (his_no,balance_no,mer_type,mer_no,amount_front,amount,amount_behind,trans_type,balance_type," +
                " service,service_order_no,result_status,result_msg,remark,create_time) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ";

        List<Object> list = new ArrayList<>();
        list.add(insertBalanceHis.getHisNo());
        list.add(insertBalanceHis.getBalanceNo());
        list.add(insertBalanceHis.getMerType());
        list.add(insertBalanceHis.getMerNo());
        list.add(insertBalanceHis.getAmountFront());
        list.add(insertBalanceHis.getAmount());
        list.add(insertBalanceHis.getAmountBehind());
        list.add(insertBalanceHis.getTransType());
        list.add(insertBalanceHis.getBalanceType());
        list.add(insertBalanceHis.getService());
        list.add(insertBalanceHis.getServiceOrderNo());
        list.add(insertBalanceHis.getResultStatus());
        list.add(insertBalanceHis.getResultMsg());
        list.add(insertBalanceHis.getRemark());
        list.add(new Date());
        return baseDao.getSaveObjId(insertSql, list.toArray());
    }

    /**
     * 根据用户和通道查询余额（通道为空则查询总余额）
     *
     * @param merNo
     * @param merType
     * @param balanceChannel
     * @return
     */
    @Override
    public Map<String, Object> getBalanceByMerAndChannel(String merNo, String merType, String balanceChannel) {

        String sql = " SELECT IFNULL(SUM(curr_balance), 0.00) AS currBalance, IFNULL(SUM(his_balance), 0.00) AS hisBalance, IFNULL(SUM(freeze_balance), 0.00) AS freezeBalance " +
                " FROM t_balance WHERE mer_type = ? AND mer_no = ? ";
        List<Object> params = new ArrayList<>();
        params.add(merType);
        params.add(merNo);

        if (StringUtils.isNotBlank(balanceChannel)) {
            sql += " AND balance_channel = ? ";
            params.add(balanceChannel);
        }
        return baseDao.findFirst(sql, params.toArray());
    }

    /**
     * 根据用户和通道查询账户信息
     *
     * @param merNo
     * @param merType
     * @param balanceChannel
     * @return
     */
    @Override
    public Balance getAccountByMerAndChannel(String merNo, String merType, String balanceChannel) throws Exception {

        String sql = " SELECT * FROM t_balance WHERE mer_type = ? AND mer_no = ? AND balance_channel = ? ";
        return baseDao.findFirst(Balance.class, sql, new Object[]{merType, merNo, balanceChannel});
    }

    /**
     * 冻结通道账户
     *
     * @param merNo
     * @param merType
     * @param amountBig
     * @param balanceChannel
     * @return
     * @throws Exception
     */
    @Override
    @Transactional
    public int freezeBalance(String merNo, String merType, BigDecimal amountBig, String balanceChannel) throws Exception {

        String sql = " UPDATE t_balance SET freeze_balance = freeze_balance + ? WHERE mer_type = ? AND mer_no = ? AND balance_channel = ? ";
        return baseDao.execute(sql, new Object[]{amountBig, merType, merNo, balanceChannel});
    }

    /**
     * 解冻通道账户
     *
     * @param merNo
     * @param merType
     * @param balanceChannel
     * @return
     * @throws Exception
     */
    @Override
    @Transactional
    public int unfreezeBalance(String merNo, String merType, String balanceChannel) throws Exception {

        String sql = " UPDATE t_balance SET freeze_balance = 0 WHERE mer_type = ? AND mer_no = ? AND balance_channel = ? ";
        return baseDao.execute(sql, new Object[]{merType, merNo, balanceChannel});
    }
}
